<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="dEOVj" id="dEOVj"><span data-lake-id="uea1306bb" id="uea1306bb">典型回答</span></h1>
  <p data-lake-id="ud2472dbe" id="ud2472dbe"><br></p>
  <p data-lake-id="uda2e56eb" id="uda2e56eb"><span data-lake-id="u41e3226e" id="u41e3226e">很多时候，日志的记录都经常被大家忽略，因为对于性能要求不高的场景中，记录日志确实是可以忽略的。</span></p>
  <p data-lake-id="u245e9a00" id="u245e9a00"><span data-lake-id="uab7452a3" id="uab7452a3">​</span><br></p>
  <p data-lake-id="u5de23459" id="u5de23459"><span data-lake-id="u69082bf1" id="u69082bf1">但是在很多核心业务链路中，1-2ms的影响都很大，所以就会考虑对写日志环境进行性能优化。希望这个技术大家都能用的上，哈哈哈哈。</span></p>
  <p data-lake-id="uedaaba2e" id="uedaaba2e"><span data-lake-id="u6180e835" id="u6180e835">​</span><br></p>
  <p data-lake-id="uae036c30" id="uae036c30"><span data-lake-id="u063d8dad" id="u063d8dad">解决日志慢的问题，主要有两种方案，一个是异步，一个是降级。</span></p>
  <p data-lake-id="u2a70ed08" id="u2a70ed08"><span data-lake-id="u0d5ca423" id="u0d5ca423">​</span><br></p>
  <p data-lake-id="uf22ae306" id="uf22ae306"><span data-lake-id="ucf5c86b8" id="ucf5c86b8">因为日志写入操作通常是一个相对耗时的IO操作，如果每次日志记录都同步写入磁盘，可能会导致主线程阻塞，影响应用程序的性能。通过使用异步日志，主线程可以继续执行其他任务，而日志写入操作则由后台线程负责处理，提高了应用程序的响应性和吞吐量。</span></p>
  <p data-lake-id="ua0ae8f6f" id="ua0ae8f6f"><br></p>
  <p data-lake-id="udb4eecce" id="udb4eecce"><span data-lake-id="u90504be4" id="u90504be4">目前常用的日志框架，如Log4j2、Logback等都是支持异步日志的。都提供了AsyncAppender来实现异步的日志写入。</span></p>
  <p data-lake-id="udc6a751d" id="udc6a751d"><span data-lake-id="u96abd8db" id="u96abd8db">​</span><br></p>
  <p data-lake-id="u1aab1527" id="u1aab1527"><span data-lake-id="u7ea3d028" id="u7ea3d028">如logback中做如下配置即可实现异步日志：</span></p>
  <p data-lake-id="u29db3f9d" id="u29db3f9d"><span data-lake-id="ue1bb06bb" id="ue1bb06bb">​</span><br></p>
  <pre lang="java"><code>
&lt;configuration&gt;
   &lt;appender name="FILE" class="ch.qos.logback.core.FileAppender"&gt;
     &lt;file&gt;application.log&lt;/file&gt;
     &lt;encoder&gt;
       &lt;pattern&gt;%logger{35} - %m%n&lt;/pattern&gt;
     &lt;/encoder&gt;
   &lt;/appender&gt;
   &lt;appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender"&gt;
      &lt;appender-ref ref="FILE" /&gt;
        &lt;queueSize&gt;512&lt;/queueSize&gt;
        &lt;discardingThreshold&gt;0&lt;/discardingThreshold&gt;
        &lt;neverBlock&gt;true&lt;/neverBlock&gt;
   &lt;/appender&gt;
   &lt;root level="DEBUG"&gt;
     &lt;appender-ref ref="ASYNC" /&gt;
   &lt;/root&gt;
 &lt;/configuration&gt;
</code></pre>
  <p data-lake-id="u0c838b3b" id="u0c838b3b"><br></p>
  <p data-lake-id="uc9da63fa" id="uc9da63fa"><span data-lake-id="u8752cca2" id="u8752cca2">其中queueSize为BlockingQueue的最大容量，默认大小为256，如果请求量大的话，可以适当调大这个queue的size。</span></p>
  <p data-lake-id="u33106b87" id="u33106b87"><span data-lake-id="u602171fa" id="u602171fa">​</span><br></p>
  <p data-lake-id="u4095098c" id="u4095098c"><span data-lake-id="ueec928f3" id="ueec928f3">discardingThreshold，日志丢弃阈值， 默认情况下，当队列还有20%容量，他将丢弃trace、debug和info级别的日志，只保留warn和error级别的日志。</span></p>
  <p data-lake-id="u2a5b5839" id="u2a5b5839"><span data-lake-id="u3429bd8b" id="u3429bd8b">​</span><br></p>
  <p data-lake-id="u0e39452c" id="u0e39452c"><span data-lake-id="u4c9a25fb" id="u4c9a25fb">neverBlock，在队列放满的情况下是否阻塞线程，默认是false，如果配置为true，当队列满了之后，后面阻塞的线程想要输出的消息就直接被丢弃，从而线程不会阻塞。适合在一些性能要求高，并且日志可降级的场景。</span></p>
  <p data-lake-id="u1218cf5d" id="u1218cf5d"><span data-lake-id="u76ae6ce6" id="u76ae6ce6">​</span><br></p>
  <p data-lake-id="u9314c7bc" id="u9314c7bc"><span data-lake-id="u5fe1883e" id="u5fe1883e">说到降级，日志降级一般用的也不多，但是在大促场景中，也是会用到的，一般是配合预案一起做，就是在极端情况下，通过开关来调节， 让日志直接不输出。或者只打印ERROR级别的日志。</span></p>
  <p data-lake-id="u57fb164f" id="u57fb164f"><span data-lake-id="ua5e6eec7" id="ua5e6eec7">​</span><br></p>
  <p data-lake-id="u7b8a0e2e" id="u7b8a0e2e"><span data-lake-id="u7488997f" id="u7488997f">或者做的更加精细一点，就是采样打印日志，比如采样1%进行日志输出，但是其实用的比较少，很多时候，采样和直接不打差别也不大。</span></p>
  <p data-lake-id="ue598653d" id="ue598653d"><span data-lake-id="u8862e189" id="u8862e189">​</span><br></p>
  <p data-lake-id="u3ff7ac9a" id="u3ff7ac9a"><span data-lake-id="ube9af453" id="ube9af453">关于日志降级，我之前做过一个工具，可以动态调整日志级别，后面我把主要思想简单说一下。</span></p>
  <p data-lake-id="u28a5b7cd" id="u28a5b7cd"><span data-lake-id="u1754476d" id="u1754476d">​</span><br></p>
  <p data-lake-id="u7bd0ed90" id="u7bd0ed90"><span data-lake-id="uc3845bdc" id="uc3845bdc">​</span><br></p>
  <h1 data-lake-id="vaN5m" id="vaN5m"><span data-lake-id="u1f3e4ea2" id="u1f3e4ea2">扩展知识</span></h1>
  <p data-lake-id="uc65eda99" id="uc65eda99"><br></p>
  <h2 data-lake-id="Dy3y5" id="Dy3y5"><span data-lake-id="u21393e2b" id="u21393e2b">异步日志的限制</span></h2>
  <p data-lake-id="u3db27e97" id="u3db27e97"><br></p>
  <p data-lake-id="u394307af" id="u394307af"><span data-lake-id="uc8820e3b" id="uc8820e3b">在很多时候，我们会在日志中打印一个链路追踪的信息，如trace_id，但是因为这里是异步打印的，ThreadLocal中存储的trace_id就无法获取了，就会导致日志中无法记录trace_id了。</span></p>
  <p data-lake-id="u51fc53fc" id="u51fc53fc"><span data-lake-id="u1b2daf4d" id="u1b2daf4d">​</span><br></p>
  <p data-lake-id="u060bfedb" id="u060bfedb"><span data-lake-id="u7fd175ed" id="u7fd175ed">​</span><br></p>
  <p data-lake-id="u01017ed1" id="u01017ed1"><span data-lake-id="u22c05eab" id="u22c05eab">所以需要特殊解决一下，方法也比较简单，就是自己定义一个Appender的实现类，在这各类里面进行设置这个trace_id：</span></p>
  <p data-lake-id="ub0641251" id="ub0641251"><span data-lake-id="uc706e360" id="uc706e360">​</span><br></p>
  <pre lang="java"><code>
MDC.put("traceId",  threadPoolTaskData.toString());
</code></pre>
  <p data-lake-id="u4e7bc643" id="u4e7bc643"><br></p>
  <p data-lake-id="u10325ce2" id="u10325ce2"><span data-lake-id="u3944b74c" id="u3944b74c">或者使用 </span><a href="https://github.com/ofpay/logback-mdc-ttl?spm=ata.21736010.0.0.31d765f2hrzaOk" target="_blank" data-lake-id="u5eb51cb2" id="u5eb51cb2"><span data-lake-id="u729757c6" id="u729757c6">https://github.com/ofpay/logback-mdc-ttl</span></a><span data-lake-id="u35f80d8f" id="u35f80d8f"> 来解决。</span></p>
  <p data-lake-id="ue3e61609" id="ue3e61609"><span data-lake-id="u3e9386c1" id="u3e9386c1">​</span><br></p>
 </body>
</html>